using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;

namespace Toenda.Foundation.Globalization.Holiday.HolidayCalenderFactories {
	/// <summary>
	/// Implements the England HolidayCalendar
	/// </summary>
	public class EnglandHolidayCalendarFactory : HolidayCalendarFactory {
		private static EnglandHolidayCalendarFactory _instance;

		/// <summary>
		/// Gets the instance fo the specific holiday calendar
		/// </summary>
		/// <param name="year"></param>
		/// <param name="eventType"></param>
		/// <returns></returns>
		public static EnglandHolidayCalendarFactory GetInstance(int year, HolidayEventType eventType) {
			if(_instance == null || year != _instance.CurrentYear) {
				_instance = new EnglandHolidayCalendarFactory(year, eventType);
				return _instance;
			}

			return _instance;
		}

		/// <summary>
		/// Creates a new instance of <see cref="EnglandHolidayCalendarFactory"/> and adds the specific holidays
		/// </summary>
		/// <param name="year"></param>
		/// <param name="eventType"></param>
		private EnglandHolidayCalendarFactory(int year, HolidayEventType eventType) {
			this.CurrentYear = year;

			this.Initialize(eventType);
		}

		/// <summary>
		/// Initialize the calendar
		/// </summary>
		/// <param name="eventType"></param>
		public override void Initialize(HolidayEventType eventType) {
			base.Holidays = new List<Holiday>();
			base.Holidays.Add(new Holiday(true, new DateTime(this.CurrentYear, 1, 1), "New Year"));

			DateTime easterSunday = GetEasterSunday();
			base.Holidays.Add(new Holiday(false, easterSunday.AddDays(-2), "Good Friday"));
			base.Holidays.Add(new Holiday(false, easterSunday.AddDays(1), "Easter Monday"));

			base.Holidays.Add(new Holiday(false, GetEarlyMayBankHoliday(), "Early May Bank Holiday"));
			base.Holidays.Add(new Holiday(false, GetSpringBankHoliday(), "Spring Bank Holiday"));
			base.Holidays.Add(new Holiday(false, GetAugustBankHoliday(), "August Bank Holiday"));
			base.Holidays.Add(
				new Holiday(
					false, 
					GetNextHoliday(new DateTime(base.CurrentYear, 12, 25), HolidayMovementType.NextThursday), 
					"1. Christmas Day"
				)
			);
			base.Holidays.Add(
				new Holiday(
					false,
					GetNextHoliday(new DateTime(base.CurrentYear, 12, 26), HolidayMovementType.NextMonday),
					"2. Christmas Day"
				)
			);

			switch(eventType) {
				case HolidayEventType.Banking:
					break;

				case HolidayEventType.Currency:
					break;

				case HolidayEventType.Settlement:
					break;

				case HolidayEventType.Trading:
					break;
			}
		}

		/// <summary>
		/// Get the next day of a holiday, if it can be moved
		/// </summary>
		/// <param name="date"></param>
		/// <param name="type"></param>
		/// <returns></returns>
		private DateTime GetNextHoliday(DateTime date, HolidayMovementType type) {
			if(date.DayOfWeek == DayOfWeek.Saturday) {
				if(type == HolidayMovementType.NextMonday) {
					date = date.AddDays(2);
				}
				else if(type == HolidayMovementType.NextThursday) {
					date = date.AddDays(3);
				}
			}

			if(date.DayOfWeek == DayOfWeek.Sunday) {
				if(type == HolidayMovementType.NextMonday) {
					date = date.AddDays(1);
				}
				else if(type == HolidayMovementType.NextThursday) {
					date = date.AddDays(2);
				}
			}

			return date;
		}

		/// <summary>
		/// Get the Easter Sunday
		/// </summary>
		/// <returns></returns>
		private DateTime GetEasterSunday() {
			int g, h, c, j, l, i;

			g = base.CurrentYear % 19;
			c = base.CurrentYear / 100;
			h = ((c - (c / 4)) - (((8 * c) + 13) / 25) + (19 * g) + 15) % 30;
			i = h - (h / 28) * (1 - (29 / (h + 1)) * ((21 - g) / 11));
			j = (base.CurrentYear + (base.CurrentYear / 4) + i + 2 - c + (c / 4)) % 7;

			l = i - j;
			int month = (int)(3 + ((l + 40) / 44));
			int day = (int)(l + 28 - 31 * (month / 4));

			return new DateTime(base.CurrentYear, month, day);
		}

		/// <summary>
		/// Get the Early-May-Bank-Holiday
		/// </summary>
		/// <returns></returns>
		private DateTime GetEarlyMayBankHoliday() {
			DateTime tmp = new DateTime(base.CurrentYear, 5, 1);
			GregorianCalendar gc = new GregorianCalendar();

			for(int i = 1; i <= gc.GetDaysInMonth(base.CurrentYear, 5); i++) {
				if(tmp.DayOfWeek == DayOfWeek.Monday) {
					break;
				}

				tmp = tmp.AddDays(1);
			}

			return tmp;
		}

		/// <summary>
		/// Get the Spring-Bank-Holiday
		/// </summary>
		/// <returns></returns>
		private DateTime GetSpringBankHoliday() {
			GregorianCalendar gc = new GregorianCalendar();
			int daysInMonth = gc.GetDaysInMonth(base.CurrentYear, 5);
			DateTime tmp = new DateTime(base.CurrentYear, 5, daysInMonth);

			for(int i = daysInMonth; i >= 1; i--) {
				if(tmp.DayOfWeek == DayOfWeek.Monday) {
					break;
				}

				tmp = tmp.AddDays(-1);
			}

			return tmp;
		}

		/// <summary>
		/// Get the August-Bank-Holiday
		/// </summary>
		/// <returns></returns>
		private DateTime GetAugustBankHoliday() {
			GregorianCalendar gc = new GregorianCalendar();
			int daysInMonth = gc.GetDaysInMonth(base.CurrentYear, 8);
			DateTime tmp = new DateTime(base.CurrentYear, 8, daysInMonth);

			for(int i = daysInMonth; i >= 1; i--) {
				if(tmp.DayOfWeek == DayOfWeek.Monday) {
					break;
				}

				tmp = tmp.AddDays(-1);
			}

			return tmp;
		}
	}
}
